package agentFramework.tools;

import java.util.ArrayList;

import agentFramework.action.Action;
import agentFramework.core.env.Environment;


public class LeaderDetector 
{
	private boolean[][]					isNeighbor;				// the communication graph
	private int							nAgents;				// the number of agents
	private ArrayList<LogData>			logList;				// the time-ordered list of actions

	// the algorithm parameters 
	private int							PHI;					// the agent count bound   ( >= PHI )
	private int							PI;						// the path bound          ( <= PI )
	private double						SIGMA;					// the influence bound     ( >= SIGMA )
	private double						RHO;					// the degradation factor
	
	private double[][]					correlation;            // measures the correlation between agents
	
	private ArrayList<InfluenceGroup>	influenceGroupList;     // the list of influence groups
	
	
	// constructor
	public LeaderDetector(int nAgents, boolean[][] isNeighbor,
						  int PHI, int PI, double SIGMA, double RHO)
	{
		this.nAgents = nAgents;
		this.isNeighbor = isNeighbor;
		this.logList = new ArrayList<LogData>();
		
		this.PHI = PHI;
		this.PI = PI;
		this.SIGMA = SIGMA;
		this.RHO = RHO;
		
		this.influenceGroupList = new ArrayList<InfluenceGroup>();
		
		this.correlation = new double[nAgents][nAgents];
		
		for (int i = 0; i < nAgents; i++)
			for (int j = 0; j < nAgents; j++)
			{
				correlation[i][j] = 0.0;
			}
	}
	
	
	// log the given info
	public void addLogData(LogData logData)
	{
		// add the log data
		this.logList.add(0, logData);

		// update the correlation matrix
		for (int i = 0; i < nAgents; i++)
		{
			Action ai = logData.getAction(i);
			
			if (ai == null) continue;
			
			for (int j = i+1; j < nAgents; j++)
			{
				Action aj = logData.getAction(j);
				
				if (aj == null) continue;
				
				if (!isNeighbor[i][j]) continue;
				
				if (ai.getType() == aj.getType())
				{
					correlation[i][j]++;
					correlation[j][i]++;
				}
			}
		}
	}

	
	// returns a copy of the current influence groups
	public ArrayList<InfluenceGroup> getInfluenceGroups()
	{
		return (ArrayList<InfluenceGroup>) influenceGroupList.clone();
	}

	
	// identifies the current leaders
	public void findLeaders()
	{			
		// clear the previous groups
		influenceGroupList.clear();
		
		// iterate through every agent and check if it satisfies the leader properties
		for (int i = 0; i < nAgents; i++)
		{
			InfluenceGroup group = new InfluenceGroup(i, getAdeptsOf(i));  
			
			if ( group.adeptList.size() >= PHI)
			{
				// we have a leader
				group.hasLeader = true;
			}
			
			influenceGroupList.add(group);
		}
	}


	// TODO: to be optimized because it is similar to Dijkstra with path length restriction
	//
	// computes the number of adepts of the root agent
	private ArrayList<Integer> getAdeptsOf(int root)
	{
		// influence[i][k] = the influence of the root over i using exactly k edges
		double[][]				influence = new double[nAgents][PI + 1];
		ArrayList<Integer> 		adeptList = new ArrayList<Integer>();
		int						N = logList.size();
				
		// initialization
		for (int i = 0; i < nAgents; i++)
		{
			influence[i][0] = 0;
		}
		
		influence[root][0] = 1;
		
		// compute the influence[][] matrix
		for (int k = 0; k < PI; k++)
		{
			for (int i = 0; i < nAgents; i++)
			{
				for (int j = 0; j < nAgents; j++)
				{
					if (!isNeighbor[i][j]) continue;
					
					influence[i][k+1] = Math.max(1.0 * RHO * influence[j][k] * correlation[j][i] / N, 
							                     influence[i][k+1]);
				}
			}
		}

		// identify and count the adepts
		for (int i = 0; i < nAgents; i++)
		{
			if (i == root) continue;
			
			for (int k = 1; k <= PI; k++)
			{
				if (influence[i][k] >= SIGMA)
				{
					adeptList.add(i);
					break;
				}
			}
		}

		return adeptList;
	}
}